package drds.global_transaction.resource_manager.model;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.util.StringUtils;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import drds.global_transaction.resource_manager.proxy.resource_manager.DataSourceProxy;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

public class TableMetaDatas
{

	private static final long CACHE_SIZE = 100000;

	private static final long EXPIRE_TIME = 900 * 1000;

	private static final Cache<String, TableMetaData> TABLE_META_CACHE = CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).expireAfterWrite(EXPIRE_TIME, TimeUnit.MILLISECONDS).softValues().build();

	private static TableMetaData buildTableMetaData(DatabaseMetaData databaseMetaData, ResultSetMetaData resultSetMetaData) throws SQLException
	{

		String schemaName = resultSetMetaData.getSchemaName(1);
		String catalogName = resultSetMetaData.getCatalogName(1);
		String tableName = resultSetMetaData.getTableName(1);
		//
		TableMetaData tableMetaData = new TableMetaData();
		tableMetaData.setTableName(tableName);

		java.sql.ResultSet columnResultSet = databaseMetaData.getColumns(catalogName, schemaName, tableName, "%");
		while (columnResultSet.next())
		{
			ColumnMetaData columnMetaData = new ColumnMetaData();
			columnMetaData.setTableCat(columnResultSet.getString("TABLE_CAT"));
			columnMetaData.setTableSchemaName(columnResultSet.getString("TABLE_SCHEM"));
			columnMetaData.setTableName(columnResultSet.getString("TABLE_NAME"));
			columnMetaData.setColumnName(columnResultSet.getString("COLUMN_NAME").toLowerCase());// 小写
			columnMetaData.setDataType(columnResultSet.getInt("DATA_TYPE"));
			columnMetaData.setDataTypeName(columnResultSet.getString("TYPE_NAME"));
			columnMetaData.setColumnSize(columnResultSet.getInt("COLUMN_SIZE"));
			columnMetaData.setDecimalDigits(columnResultSet.getInt("DECIMAL_DIGITS"));
			columnMetaData.setNumPrecRadix(columnResultSet.getInt("NUM_PREC_RADIX"));
			columnMetaData.setNullAble(columnResultSet.getInt("NULLABLE"));
			columnMetaData.setRemarks(columnResultSet.getString("REMARKS"));
			columnMetaData.setColumnDef(columnResultSet.getString("COLUMN_DEF"));
			columnMetaData.setSqlDataType(columnResultSet.getInt("SQL_DATA_TYPE"));
			columnMetaData.setSqlDatetimeSub(columnResultSet.getInt("SQL_DATETIME_SUB"));
			columnMetaData.setCharOctetLength(columnResultSet.getInt("CHAR_OCTET_LENGTH"));
			columnMetaData.setOrdinalPosition(columnResultSet.getInt("ORDINAL_POSITION"));
			columnMetaData.setIsNullAble(columnResultSet.getString("IS_NULLABLE"));
			columnMetaData.setIsAutoincrement(columnResultSet.getString("IS_AUTOINCREMENT"));

			tableMetaData.getColumnNameToColumnMetaDataMap().put(columnMetaData.getColumnName(), columnMetaData);
		}

		java.sql.ResultSet indexResultSet = databaseMetaData.getIndexInfo(catalogName, schemaName, tableName, false, true);
		while (indexResultSet.next())
		{
			String columnName = indexResultSet.getString("COLUMN_NAME").toLowerCase();// 小写
			String indexName = indexResultSet.getString("INDEX_NAME");
			//
			ColumnMetaData columnMetaData = tableMetaData.getColumnNameToColumnMetaDataMap().get(columnName);
			/**
			 * 存在一个索引多个列
			 */
			if (tableMetaData.getColumnNameToIndexMetaDataMap().containsKey(indexName))
			{
				IndexMetaData indexMetaData = tableMetaData.getColumnNameToIndexMetaDataMap().get(indexName);
				indexMetaData.getColumnMetaDataList().add(columnMetaData);
			} else
			{
				IndexMetaData indexMetaData = new IndexMetaData();
				indexMetaData.setIndexName(indexName);
				indexMetaData.setNonUnique(indexResultSet.getBoolean("NON_UNIQUE"));
				indexMetaData.setIndexQualifier(indexResultSet.getString("INDEX_QUALIFIER"));
				indexMetaData.setIndexName(indexResultSet.getString("INDEX_NAME"));
				indexMetaData.setType(indexResultSet.getShort("TYPE"));
				indexMetaData.setOrdinalPosition(indexResultSet.getShort("ORDINAL_POSITION"));
				indexMetaData.setAscOrDesc(indexResultSet.getString("ASC_OR_DESC"));
				indexMetaData.setCardinality(indexResultSet.getInt("CARDINALITY"));
				indexMetaData.getColumnMetaDataList().add(columnMetaData);
				if ("PRIMARY".equalsIgnoreCase(indexName) || indexName.equalsIgnoreCase(resultSetMetaData.getTableName(1) + "_pkey"))
				{
					indexMetaData.setIndextype(IndexType.primary_key);
				} else if (indexMetaData.isNonUnique() == false)
				{
					indexMetaData.setIndextype(IndexType.unique);
				} else
				{
					indexMetaData.setIndextype(IndexType.normal);
				}
				tableMetaData.getColumnNameToIndexMetaDataMap().put(indexName, indexMetaData);

			}
		}
		return tableMetaData;
	}

	private static TableMetaData buildTableMetaData(DruidDataSource druidDataSource, String tableName) throws SQLException
	{
		Connection connection = null;
		java.sql.Statement statement = null;
		java.sql.ResultSet resultSet = null;
		try
		{
			connection = druidDataSource.getConnection();
			statement = connection.createStatement();
			StringBuilder sb = new StringBuilder("SELECT * FROM " + tableName + " LIMIT 1");
			resultSet = statement.executeQuery(sb.toString());
			ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
			DatabaseMetaData databaseMetaData = connection.getMetaData();
			return buildTableMetaData(databaseMetaData, resultSetMetaData);
		} catch (Exception e)
		{
			if (e instanceof SQLException)
			{
				throw ((SQLException) e);
			}
			throw new SQLException("failed to fetch schema of " + tableName, e);

		} finally
		{
			if (resultSet != null)
			{
				resultSet.close();
			}
			if (statement != null)
			{
				statement.close();
			}
			if (connection != null)
			{
				connection.close();
			}
		}
	}

	public static TableMetaData getTableMetaData(DataSourceProxy dataSourceProxy, String tableName)
	{
		return getTableMetaData(dataSourceProxy.getDruidDataSource(), tableName);
	}

	public static TableMetaData getTableMetaData(final DruidDataSource druidDataSource, final String tableName)
	{
		if (StringUtils.isEmpty(tableName))
		{
			throw new IllegalArgumentException("TableMetaData cannot be fetched without tableName");
		}

		String druidDataSourceUrl = druidDataSource.getUrl();

		TableMetaData tableMetaData = null;
		final String key = druidDataSourceUrl + "." + tableName;
		try
		{
			tableMetaData = TABLE_META_CACHE.get(key, new Callable<TableMetaData>()
			{
				@Override
				public TableMetaData call() throws Exception
				{
					return buildTableMetaData(druidDataSource, tableName);
				}
			});
		} catch (ExecutionException e)
		{
		}

		if (tableMetaData == null)
		{
			try
			{
				tableMetaData = buildTableMetaData(druidDataSource, tableName);
			} catch (SQLException e)
			{
			}
		}

		if (tableMetaData == null)
		{
			throw new NullPointerException("tableMetaData of tableName:" + tableName);
		}
		return tableMetaData;
	}
}
